﻿using Orleans.Concurrency;
using Orleans.Providers;
using Orleans.Transactions.Abstractions;


namespace orleans_demo.Services
{

    public interface ITransactionalExample : IGrainWithGuidKey
    {
        /// <summary>
        /// 调用是事务性的，但只能在现有事务的上下文中调用。
        /// </summary>
        /// <param name="amount"></param>
        /// <returns></returns>
        [Transaction(TransactionOption.Join)]
        Task Deposit(decimal amount);
        [Transaction(TransactionOption.Join)]
        Task Withdraw(decimal amount);
        /// <summary>
        /// 调用是事务性的。 如果在事务的上下文中调用，则它将使用该上下文；否则它将创建新的上下文。
        /// </summary>
        /// <returns></returns>
        [Transaction(TransactionOption.CreateOrJoin)]
        Task<decimal> GetBalance();
    }

    /// <summary>
    /// 必须使用ReentrantAttribute标记事务 grain，以确保事务上下文正确传递到 grain 调用。
    /// </summary>
    [Reentrant]
    [StorageProvider(ProviderName = "TransactionStore")]
    public class TransactionalExample : Grain, ITransactionalExample
    {
        private readonly ITransactionalState<Balance> _balance;

        public override Task OnActivateAsync(CancellationToken cancellationToken)
        {
            return base.OnActivateAsync(cancellationToken);
        }

        public TransactionalExample(
            [TransactionalState(nameof(balance), "TransactionStore")]
           ITransactionalState<Balance> balance) =>
            _balance = balance ?? throw new ArgumentNullException(nameof(balance));

        public async Task Deposit(decimal amount)
        {
            await _balance.PerformUpdate(
                balance => balance.Value += amount);
        }

        public Task Withdraw(decimal amount) =>
            _balance.PerformUpdate(balance =>
            {
                if (balance.Value < amount)
                {
                    throw new InvalidOperationException(
                        $"Withdrawing {amount} credits from account " +
                        $"\"{this.GetPrimaryKeyString()}\" would overdraw it." +
                        $" This account has {balance.Value} credits.");
                }

                balance.Value -= amount;
            });

        public Task<decimal> GetBalance() =>
            _balance.PerformRead(balance => balance.Value);
    }

    [GenerateSerializer]
    public record class Balance
    {
        [Id(0)]
        public decimal Value { get; set; } = 1_000;
    }


    //使用这种方式调用事务也可以

    public interface IAtmGrain :IGrainWithGuidKey
    {
        [Transaction(TransactionOption.Create)]
        Task Transfer(
           Guid fromId,
           Guid toId,
           decimal amount);
    }

    [StatelessWorker]
    [StorageProvider(ProviderName = "TransactionStore")]
    public class AtmGrain : Grain, IAtmGrain
    {

        public override Task OnActivateAsync(CancellationToken cancellationToken)
        {
            return base.OnActivateAsync(cancellationToken);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="fromId"></param>
        /// <param name="toId"></param>
        /// <param name="amount"></param>
        /// <returns></returns>
        public async Task Transfer(
            Guid fromId,
            Guid toId,
            decimal amount)
        {
           await Task.WhenAll(
                GrainFactory.GetGrain<ITransactionalExample>(fromId).Withdraw(amount),
                GrainFactory.GetGrain<ITransactionalExample>(toId).Deposit(amount));
        }
    }


    //public class CustomTransactionalStorageFactory : ITransactionalStateStorageFactory
    //{
    //    public ITransactionalStateStorage<TState> Create<TState>(string stateName, IGrainContext context)
    //        where TState : class, new()
    //    {
    //        return new CustomTransactionalStorage<TState>();
    //    }
    //}

    //public class CustomTransactionalStorage: ITransactionalStateStorage
    //{

    //}

}
